public class SearchPanel : DocPanelBase
{
	protected Gtk.Entry pharse_entry;
	protected Gtk.CheckButton match_case_cbtn;
	protected Gtk.CheckButton match_word_cbtn;
	protected Gtk.CheckButton match_regex_cbtn;

	protected GLib.List<weak Gtk.TextMark> founded;	
 	protected int founded_pos = -1;
 
 	public bool match_case
 	{
 		get { return this.match_case_cbtn.get_active (); }
 		set { this.match_case_cbtn.set_active (value);   }
 	}
 
 	public bool match_words 
 	{
 		get { return this.match_word_cbtn.get_active (); }
 		set { this.match_word_cbtn.set_active (value);   }
 	}
 
 	public bool match_regex
 	{
 		get { return this.match_regex_cbtn.get_active (); }
 		set { this.match_regex_cbtn.set_active (value);   }
 	}
 	
 	public string search_pharse  
 	{
 		get { return this.pharse_entry.get_text (); }
 		set { this.pharse_entry.set_text (value);   }
 	}
 
	public SearchPanel (ref Gtk.SourceBuffer source_buff, ref Gtk.SourceView source_view)
	{
		base (3, 6, false, ref source_buff, ref source_view);

		this.attach (
			new Gtk.Image.from_stock (Gtk.STOCK_FIND, Gtk.IconSize.MENU),
			0, 1, 0, 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK, 5, 0
		);
	
		Gtk.Label pharse_entry_lbl = new Gtk.Label.with_mnemonic ("<b>_Fraza:</b> ");
		pharse_entry_lbl.set_use_markup (true);
		this.attach (pharse_entry_lbl, 1, 2, 0, 1, Gtk.AttachOptions.SHRINK, Gtk.AttachOptions.SHRINK, 5, 0);

		this.pharse_entry = new Gtk.Entry ();		
		pharse_entry_lbl.set_mnemonic_widget (this.pharse_entry);
		this.attach_defaults (this.pharse_entry, 2, 3, 0, 1);
		
		Gtk.HSeparator options_sep = new Gtk.HSeparator ();
		this.attach (options_sep, 0, 6, 1, 2, Gtk.AttachOptions.FILL, Gtk.AttachOptions.FILL, 0, 3);
		
		Gtk.HBox options_box = new Gtk.HBox (false, 0);
				
		this.match_case_cbtn = new Gtk.CheckButton.with_mnemonic ("Rozr. wielkość _liter");
		this.match_case_cbtn.set_active (false);
		this.match_case_cbtn.toggled += this.on_option_toggled;
		options_box.pack_start (this.match_case_cbtn, false, false, 5);
		this.match_word_cbtn = new Gtk.CheckButton.with_mnemonic ("Tylko _słowa");
		this.match_word_cbtn.set_active (false);
		this.match_word_cbtn.toggled += this.on_option_toggled;
		options_box.pack_start (this.match_word_cbtn, false, false, 5);
		this.match_regex_cbtn = new Gtk.CheckButton.with_mnemonic ("Wyr. _reguralne");
		this.match_regex_cbtn.set_active (false);
		this.match_regex_cbtn.toggled += this.on_option_toggled;
		options_box.pack_start (this.match_regex_cbtn, false, false, 5);
		this.attach_defaults (options_box, 0, 4, 2, 3);
		
		this.pharse_entry.key_press_event += this.on_search;
		this.pharse_entry.changed += this.on_pharse_entry_changed;
		this.show_panel += this.on_show_panel;
		this.close_panel += this.on_close_panel;		
		
		if (this.source_buff.get_tag_table().lookup ("match") == null)
		{
			this.source_buff.create_tag ("match").background = "#ffff00";
		}		
	}
	
	protected void on_option_toggled ()
	{
		this.pharse_entry.grab_focus ();
		this.clear_last_search ();	
	}
	
	protected virtual bool on_show_panel ()
	{
		this.pharse_entry.grab_focus ();
		this.pharse_entry.modify_base (Gtk.StateType.NORMAL, null);
		
		return false;
	}
	
	protected virtual bool on_close_panel ()
	{			
		this.source_view.grab_focus ();	
		this.clear_last_search ();

		UIManager.get_instance().get_action ("search_find_next").set_sensitive (false);
		UIManager.get_instance().get_action ("search_find_prev").set_sensitive (false);
		
		return false;
	}
	
	protected void on_pharse_entry_changed ()
	{
		if (this.founded.length () == 0)
		{
			this.pharse_entry.modify_base (Gtk.StateType.NORMAL, null);
		}
	}
	
	protected virtual bool on_search (Gtk.Entry sender, Gdk.EventKey event)
	{		
		if (Gdk.keyval_name (event.keyval) == "Return")
		{	
			if (this.search_pharse  .length == 0)
			{
				Utils.set_widget_warrnig_background (ref this.pharse_entry);
				
				return true;
			}
															
			this.clear_last_search ();
			if (!(this.match_regex) ? this.search () : this.search_by_regex ())
			{
					UIManager.get_instance().get_action ("search_find_next").set_sensitive (true);
					UIManager.get_instance().get_action ("search_find_prev").set_sensitive (true);			
					
					this.goto_next_match ();			
			}
			else
			{
				Utils.set_widget_warrnig_background (ref this.pharse_entry);
			}
		}
		return false;
	}

	
	public void goto_next_match () 
	{
		if (this.founded_pos == -1 || this.founded_pos == this.founded.length () - 2)
		{
			this.founded_pos = 0;
		}
		else 
		{
			this.founded_pos += 2;		
		}		
		this.goto_match ();	
	}

	public void goto_prev_match ()
	{
		if (this.founded_pos == -1 || this.founded_pos == 0)
		{
			this.founded_pos = (int) this.founded.length () - 2;
		}
		else 
		{
			this.founded_pos -= 2;		
		}
		this.goto_match ();
	}
	
	private bool search ()
	{
		Gtk.TextIter start_iter; 
		this.source_buff.get_start_iter (out start_iter);		
		
		Gtk.TextIter match_start; Gtk.TextIter match_end;
		bool found = Gtk.source_iter_forward_search (
			start_iter,
			this.search_pharse  , 
			((this.match_case == false) ? Gtk.SourceSearchFlags.TEXT_ONLY | Gtk.SourceSearchFlags.CASE_INSENSITIVE : Gtk.SourceSearchFlags.TEXT_ONLY),
			out match_start, out match_end,
			null
		);
		
		while (found)
		{
			if (this.match_words && !this.range_is_word (match_start, match_end))
			{
				match_start.forward_char ();
				start_iter = match_start; 
				found = Gtk.source_iter_forward_search (
					start_iter,
					this.search_pharse  , 
					((this.match_case == true) ? Gtk.SourceSearchFlags.TEXT_ONLY | Gtk.SourceSearchFlags.CASE_INSENSITIVE : Gtk.SourceSearchFlags.TEXT_ONLY),
					out match_start, out match_end,
					null
				);
				continue;				
			}

			//Zaznaczamy dopasowany ciąg					
			this.source_buff.apply_tag_by_name ("match", match_start, match_end);
			
			this.founded.append (this.source_buff.create_mark (null, match_start, false));
			this.founded.append (this.source_buff.create_mark (null, match_end, false));			
			
			match_start.forward_char ();
			start_iter = match_start; 
			found = Gtk.source_iter_forward_search (
				start_iter,
				this.search_pharse  , 
				((this.match_case == true) ? Gtk.SourceSearchFlags.TEXT_ONLY : Gtk.SourceSearchFlags.CASE_INSENSITIVE),
				out match_start, out match_end,
				null
			);					
		}
		return (this.founded.length () > 0);
	}
	
	private bool search_by_regex ()
	{
		try
		{
			GLib.RegexCompileFlags 	regex_compile_flags = GLib.RegexCompileFlags.MULTILINE ;
			if (!this.match_case)	
			{
				regex_compile_flags |= GLib.RegexCompileFlags.CASELESS;
			}	
			GLib.RegexMatchFlags 	regex_match_flags   = GLib.RegexMatchFlags.NEWLINE_ANY; 	 

			//Ustawiamy wzorzec
			GLib.Regex pattern = new GLib.Regex (
				this.search_pharse  ,
				regex_compile_flags, 
				regex_match_flags									
			);
			
			//Pobieramy tekst z bufora
			Gtk.TextIter text_start; Gtk.TextIter text_end;
			this.source_buff.get_start_iter (out text_start); 
			this.source_buff.get_end_iter (out text_end);
			string text = this.source_buff.get_text (text_start, text_end, true);
			
			GLib.MatchInfo  match; 
			//Wyszukujemy:
			if (pattern.match_full (text, (int) text.length, 0, regex_match_flags, out match))
			{
				while (match.matches ())
				{
					int start_pos = 0, end_pos = 0;
					match.fetch_pos (0, out start_pos, out end_pos);
					//Korygujemy pozycje, patrz notatka nr. 3 
					char* p_text = text.offset (0);
					start_pos = (int) text.pointer_to_offset ((string*) (p_text + start_pos));
					end_pos   = (int) text.pointer_to_offset ((string*) (p_text + end_pos));					
					
					Gtk.TextIter match_start; Gtk.TextIter match_end;
					this.source_buff.get_iter_at_offset (out match_start, start_pos);
					this.source_buff.get_iter_at_offset (out match_end, end_pos);		
															
					if (!this.match_words && !this.range_is_word (match_start, match_end))
					{			
						match.next ();
						continue;								
					}
				
					//Zaznaczamy dopasowany ciąg					
					this.source_buff.apply_tag_by_name ("match", match_start, match_end);				
					
					this.founded.append (this.source_buff.create_mark (null, match_start, false));
					this.founded.append (this.source_buff.create_mark (null, match_end, false));
					
					match.next ();							
				}									
			}
			return (this.founded.length () > 0);
		}
		catch (GLib.RegexError er)
		{
			//TODO: Obsłga błędu...
			return false;			
		}
	}

	private inline bool range_is_word (Gtk.TextIter range_start, Gtk.TextIter range_end)
	{
		return (range_start.starts_word () && range_end.ends_word ());
	}
	
	private void goto_match ()
	{
		Gtk.TextIter start; Gtk.TextIter end;
		this.source_buff.get_iter_at_mark (out start, this.founded.nth_data (this.founded_pos));
		this.source_buff.get_iter_at_mark (out end, this.founded.nth_data (this.founded_pos+1));		
				
		this.source_buff.place_cursor (start);							 		
		this.source_buff.select_range (start, end);
		this.source_view.scroll_to_mark (this.founded.nth_data (this.founded_pos), 0.1, false, 0, 0);			
	}
	
	private void clear_last_search ()
	{
		this.founded_pos = -1; 
		
		for (int i = (int) this.founded.length () - 1; i >= 0; --i)
		{
			weak Gtk.TextMark mark = this.founded.nth_data (i);
			
			this.source_buff.delete_mark (mark);			
			this.founded.remove (mark);	
		}
		
		Gtk.TextIter start; Gtk.TextIter end;
		this.source_buff.get_start_iter (out start);
		this.source_buff.get_end_iter (out end);
		this.source_buff.remove_tag_by_name ("match", start, end);
		
		this.pharse_entry.modify_base (Gtk.StateType.NORMAL, null);
	}	
}

